"""
Directory Tree Service
Provides hierarchical directory structure for telemetry metrics
"""

from typing import Dict, List, Optional, Tuple, Any
from pathlib import Path
from collections import defaultdict
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_
import logging

from app.models.extractor import Metric, MetricOwner
from app.models.business import TeamFileAssignment, Team

logger = logging.getLogger(__name__)


class DirectoryNode:
    """Represents a node in the directory tree structure"""

    def __init__(
        self,
        name: str,
        path: str,
        node_type: str = "directory",
        parent: Optional['DirectoryNode'] = None,
        level: int = 0
    ):
        self.name = name
        self.path = path
        self.type = node_type  # "directory" or "file"
        self.parent = parent
        self.level = level
        self.children: List['DirectoryNode'] = []

        # Statistics
        self.metric_count = 0
        self.direct_metric_count = 0  # Metrics directly in this file/directory
        self.file_count = 0
        self.assigned_teams: List[str] = []
        self.components: List[str] = []
        self.metric_types: Dict[str, int] = defaultdict(int)
        self.analysis_stats: Dict[str, int] = defaultdict(int)

        # For files only
        self.file_metrics: List[Dict[str, Any]] = []

    def add_child(self, child: 'DirectoryNode'):
        """Add a child node"""
        child.parent = self
        child.level = self.level + 1
        self.children.append(child)

    def to_dict(self, include_metrics: bool = False, max_depth: Optional[int] = None) -> Dict[str, Any]:
        """Convert to dictionary format for API response"""
        result = {
            "name": self.name,
            "path": self.path,
            "type": self.type,
            "metric_count": self.metric_count,
            "file_count": self.file_count,
            "assigned_teams": self.assigned_teams,
            "components": list(set(self.components)),
            "level": self.level
        }

        # Include detailed statistics
        if self.metric_types:
            result["metric_types"] = dict(self.metric_types)

        if self.analysis_stats:
            result["analysis_stats"] = dict(self.analysis_stats)

        # Include children if within depth limit
        if self.children and (max_depth is None or self.level < max_depth):
            result["children"] = [child.to_dict(include_metrics, max_depth) for child in self.children]

            # Sort children by name (directories first, then files)
            result["children"].sort(key=lambda x: (0 if x["type"] == "directory" else 1, x["name"]))

        # Include file metrics for files
        if self.type == "file" and include_metrics:
            result["metrics"] = self.file_metrics

        return result


class DirectoryTreeService:
    """Service for building directory tree structure from telemetry metrics"""

    def __init__(self):
        self.root_nodes: List[DirectoryNode] = []

    async def build_directory_tree(
        self,
        extractor_db: AsyncSession,
        business_db: AsyncSession,
        root_path: str = "tools",
        max_depth: int = 10,
        include_metrics: bool = False,
        team_filter: Optional[str] = None,
        component_filter: Optional[str] = None,
        type_filter: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Build hierarchical directory tree from telemetry metrics

        Args:
            extractor_db: Extractor database session
            business_db: Business database session
            root_path: Root directory to start from
            max_depth: Maximum depth of tree
            include_metrics: Whether to include detailed metric information
            team_filter: Filter by team assignment
            component_filter: Filter by component
            type_filter: Filter by metric type (histogram, enum, ukm_event, ukm_metric)

        Returns:
            Dictionary containing directory tree structure and statistics
        """
        try:
            logger.info(f"🌳 [DirectoryTreeService] Building directory tree from root: {root_path}")

            # Get all metrics with file paths
            from sqlalchemy import func

            # Create a subquery for aggregated owners to avoid duplicates
            owners_subquery = select(
                MetricOwner.metric_id,
                func.group_concat(MetricOwner.owner_email, ', ').label('owner_name')
            ).group_by(MetricOwner.metric_id).subquery()

            query = select(
                Metric.id,
                Metric.name,
                Metric.type,
                Metric.summary,
                Metric.units,
                Metric.component,
                Metric.file_path,
                Metric.line_number,
                Metric.analysis_status,
                owners_subquery.c.owner_name
            ).outerjoin(
                owners_subquery, Metric.id == owners_subquery.c.metric_id
            )

            # Apply filters
            filters = []
            if component_filter:
                filters.append(Metric.component == component_filter)
            if type_filter:
                filters.append(Metric.type == type_filter)

            if filters:
                query = query.where(and_(*filters))

            # Execute query
            result = await extractor_db.execute(query)
            metrics_data = result.fetchall()

            logger.info(f"📊 [DirectoryTreeService] Retrieved {len(metrics_data)} metrics from database")

            # Build tree structure
            root_nodes = await self._build_tree_from_metrics(
                metrics_data,
                business_db,
                root_path,
                team_filter
            )

            # Calculate global statistics
            stats = self._calculate_tree_statistics(root_nodes)

            # Convert to response format
            tree_data = [
                node.to_dict(include_metrics, max_depth)
                for node in root_nodes
            ]

            response = {
                "success": True,
                "data": tree_data[0] if tree_data else None,  # Single root for now
                "stats": stats,
                "root_path": root_path,
                "total_metrics": len(metrics_data),
                "filters_applied": {
                    "team_filter": team_filter,
                    "component_filter": component_filter,
                    "type_filter": type_filter
                }
            }

            logger.info(f"✅ [DirectoryTreeService] Directory tree built successfully")
            return response

        except Exception as e:
            logger.error(f"❌ [DirectoryTreeService] Failed to build directory tree: {str(e)}")
            raise

    async def _build_tree_from_metrics(
        self,
        metrics_data: List,
        business_db: AsyncSession,
        root_path: str,
        team_filter: Optional[str] = None
    ) -> List[DirectoryNode]:
        """Build tree structure from metrics data"""

        # Group metrics by file path
        path_metrics: Dict[str, List] = defaultdict(list)
        component_set: set = set()

        for metric in metrics_data:
            file_path = metric.file_path
            if file_path.startswith(root_path):
                path_metrics[file_path].append(metric)

                if metric.component:
                    component_set.add(metric.component)

        logger.info(f"📁 [DirectoryTreeService] Processing {len(path_metrics)} unique file paths")

        # Get team assignments for all unique paths
        team_assignments = await self._get_team_assignments(
            list(path_metrics.keys()), business_db
        )

        # Build tree structure
        path_node_map: Dict[str, DirectoryNode] = {}
        root_nodes: List[DirectoryNode] = []

        # Sort paths to ensure consistent processing order
        sorted_paths = sorted(path_metrics.keys())

        for file_path in sorted_paths:
            # Skip files that don't match the root_path
            if root_path and not file_path.startswith(root_path):
                continue

            # Create nodes for each directory level in the path
            path_obj = Path(file_path)
            current_path = ""

            for i, part in enumerate(path_obj.parts):
                current_path = str(Path(current_path) / part) if current_path else part

                # Create node if it doesn't exist
                if current_path not in path_node_map:
                    node_type = "file" if i == len(path_obj.parts) - 1 else "directory"
                    node = DirectoryNode(
                        name=part,
                        path=current_path,
                        node_type=node_type,
                        level=i
                    )
                    path_node_map[current_path] = node

                    # Add to parent or root
                    if i > 0:
                        parent_path = str(Path(*path_obj.parts[:i]))
                        if parent_path in path_node_map:
                            path_node_map[parent_path].add_child(node)
                    else:
                        # Only add as root if this path matches the root_path
                        # or if root_path is not specified
                        if not root_path or current_path == root_path:
                            root_nodes.append(node)
                        # If this is a parent of the root_path, we still need to create the node
                        # but don't add it to root_nodes yet

                node = path_node_map[current_path]

                # Add metrics if this is the file level
                if i == len(path_obj.parts) - 1:
                    metrics = path_metrics[file_path]

                    # Apply team filter if specified
                    if team_filter and team_assignments.get(file_path):
                        if team_assignments[file_path].get("team_id") != team_filter:
                            continue
                    elif team_filter and not team_assignments.get(file_path):
                        continue

                    node.direct_metric_count = len(metrics)
                    node.file_count = 1
                    node.file_metrics = []

                    # Process each metric
                    for metric in metrics:
                        # Update type and component statistics
                        node.metric_types[metric.type] += 1
                        if metric.component:
                            node.components.append(metric.component)

                        # Update analysis status statistics
                        analysis_status = metric.analysis_status or 'NOT_ANALYZED'
                        node.analysis_stats[analysis_status] += 1

                        # Create metric data
                        metric_data = {
                            "id": metric.id,
                            "name": metric.name,
                            "type": metric.type,
                            "description": metric.summary,
                            "units": metric.units,
                            "component": metric.component,
                            "line_number": metric.line_number,
                            "analysis_status": analysis_status,
                            "owner": metric.owner_name
                        }

                        # Add team assignment
                        if file_path in team_assignments:
                            metric_data["assigned_team_id"] = team_assignments[file_path]["team_id"]
                            metric_data["assigned_team_name"] = team_assignments[file_path]["team_name"]

                        node.file_metrics.append(metric_data)

                    # Set team assignments for the node
                    if file_path in team_assignments:
                        team_info = team_assignments[file_path]
                        if team_info["team_name"] not in node.assigned_teams:
                            node.assigned_teams.append(team_info["team_name"])

        # If root_path is specified and exists in path_node_map but not in root_nodes,
        # add it as a root node
        if root_path and root_path in path_node_map and not root_nodes:
            root_nodes.append(path_node_map[root_path])

        # Aggregate statistics up the tree
        for root in root_nodes:
            self._aggregate_statistics(root)

        return root_nodes

    async def _get_team_assignments(
        self,
        file_paths: List[str],
        business_db: AsyncSession
    ) -> Dict[str, Dict[str, str]]:
        """Get team assignments for file paths"""

        if not file_paths:
            return {}

        # Get all active team assignments
        query = select(
            TeamFileAssignment.team_id,
            Team.name.label('team_name'),
            TeamFileAssignment.file_pattern
        ).join(
            Team, TeamFileAssignment.team_id == Team.id
        ).where(
            and_(
                TeamFileAssignment.is_active == True,
                TeamFileAssignment.file_pattern != ''
            )
        )

        result = await business_db.execute(query)
        assignments = result.fetchall()

        team_assignments: Dict[str, Dict[str, str]] = {}

        # Check each file against each assignment pattern
        for file_path in file_paths:
            for assignment in assignments:
                if self._match_file_pattern(file_path, assignment.file_pattern):
                    team_assignments[file_path] = {
                        "team_id": assignment.team_id,
                        "team_name": assignment.team_name,
                        "pattern": assignment.file_pattern
                    }
                    break  # Use first matching pattern

        logger.info(f"🏢 [DirectoryTreeService] Found team assignments for {len(team_assignments)} files")
        return team_assignments

    def _match_file_pattern(self, file_path: str, pattern: str) -> bool:
        """Check if file path matches pattern (glob style)"""
        import fnmatch
        return fnmatch.fnmatch(file_path, pattern)

    def _aggregate_statistics(self, node: DirectoryNode):
        """Aggregate statistics from children up to parent"""

        # Initialize with own statistics
        node.metric_count = node.direct_metric_count

        # Aggregate from children
        for child in node.children:
            self._aggregate_statistics(child)
            node.metric_count += child.metric_count
            node.file_count += child.file_count

            # Merge team assignments
            for team in child.assigned_teams:
                if team not in node.assigned_teams:
                    node.assigned_teams.append(team)

            # Merge components
            node.components.extend(child.components)

            # Merge metric types
            for mtype, count in child.metric_types.items():
                node.metric_types[mtype] += count

            # Merge analysis stats
            for status, count in child.analysis_stats.items():
                node.analysis_stats[status] += count

        # Remove duplicates from components
        node.components = list(set(node.components))

        # Sort lists for consistent output
        node.assigned_teams.sort()
        node.components.sort()

    def _calculate_tree_statistics(self, root_nodes: List[DirectoryNode]) -> Dict[str, Any]:
        """Calculate overall tree statistics"""

        total_directories = 0
        total_files = 0
        total_metrics = 0
        deepest_depth = 0
        all_metric_types = defaultdict(int)
        all_analysis_stats = defaultdict(int)
        all_teams = set()
        all_components = set()

        def count_nodes(node: DirectoryNode):
            nonlocal total_directories, total_files, total_metrics, deepest_depth, all_metric_types, all_analysis_stats, all_teams, all_components

            if node.type == "directory":
                total_directories += 1
                deepest_depth = max(deepest_depth, node.level)
            else:
                total_files += 1
                # Only count direct metrics for files to avoid double counting
                total_metrics += node.direct_metric_count

                # Only aggregate types and stats from files (leaf nodes) to avoid double counting
                for mtype, count in node.metric_types.items():
                    all_metric_types[mtype] += count
                for status, count in node.analysis_stats.items():
                    all_analysis_stats[status] += count

                all_teams.update(node.assigned_teams)
                all_components.update(node.components)

            for child in node.children:
                count_nodes(child)

        # Count all nodes
        for root in root_nodes:
            count_nodes(root)

        return {
            "total_directories": total_directories,
            "total_files": total_files,
            "total_metrics": total_metrics,
            "deepest_depth": deepest_depth,
            "metric_types": dict(all_metric_types),
            "analysis_stats": dict(all_analysis_stats),
            "unique_teams": len(all_teams),
            "unique_components": len(all_components),
            "root_nodes_count": len(root_nodes)
        }


# Export the main service
directory_tree_service = DirectoryTreeService()